import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
import logger from "@shared/logger/main-logger";
import type {
  OpenAIToolCall,
  ToolCallResult,
  ToolCallStatus,
} from "@shared/types/openai";
import { inject, injectable } from "inversify";
import { TYPES } from "../shared/types";
import type { Events } from "./event-service";
import { EventNames, emitter, sendToRenderer } from "./event-service";
import type { McpService } from "./mcp-service";
import type { MessageService } from "./message-service";

/**
 * MCP Tool Executor Service
 *
 * Handles the execution of tools called by AI models through the Model Context Protocol.
 *
 * Key responsibilities:
 * - Execute individual and batch tool calls with proper error handling
 * - Manage tool execution timeouts to prevent hanging operations
 * - Convert MCP tool results to formats expected by AI models
 * - Emit real-time events for UI progress tracking
 * - Handle complex result parsing and formatting for optimal AI consumption
 */
@injectable()
export class McpToolExecutor {
  /** Maximum time allowed for tool execution before timeout */
  private static readonly TOOL_CALL_TIMEOUT = 30000; // 30 seconds

  /** Maximum number of retry attempts for failed tool calls */
  private static readonly MAX_RETRY_ATTEMPTS = 3;

  constructor(
    @inject(TYPES.McpService)
    private mcpService: McpService,
    @inject(TYPES.MessageService)
    private messageService: MessageService,
  ) {}

  /**
   * Execute a single MCP tool call
   */
  async executeToolCall(
    toolCall: OpenAIToolCall,
    threadId: string,
    messageId: string,
    serverIds: string[],
  ): Promise<ToolCallResult> {
    const startTime = Date.now();
    const { id, function: toolFunction } = toolCall;
    const { name: toolName, arguments: toolArgs } = toolFunction;

    logger.info("Executing MCP tool call", {
      toolCallId: id,
      toolName,
      threadId,
      messageId,
      serverIds,
    });

    // Find the MCP server that has this tool FIRST
    const mcpServerId = await this.findServerForTool(toolName, serverIds);
    if (!mcpServerId) {
      const error = `Tool '${toolName}' not found in any of the selected MCP servers`;
      logger.error("Tool not found in MCP servers", {
        toolCallId: id,
        toolName,
        serverIds,
      });

      // Create result with error
      const result: ToolCallResult = {
        id,
        name: toolName,
        arguments: toolArgs,
        status: "error" as ToolCallStatus,
        error,
      };

      sendToRenderer(EventNames.TOOL_CALL_ERROR, {
        threadId,
        messageId,
        toolCallId: id,
        toolName,
        error,
      });

      return result;
    }

    // Check if this tool requires user approval
    const requiresApproval = await this.checkToolRequiresApproval(mcpServerId);

    // Emit tool call start event
    sendToRenderer(EventNames.TOOL_CALL_START, {
      threadId,
      messageId,
      toolCallId: id,
      toolName,
    });

    const result: ToolCallResult = {
      id,
      name: toolName,
      arguments: toolArgs,
      status: requiresApproval
        ? ("awaiting_approval" as ToolCallStatus)
        : ("pending" as ToolCallStatus),
      mcpServerId, // Set mcpServerId immediately
      requiresApproval,
    };

    // If approval is required, wait for user approval before executing
    if (requiresApproval) {
      // Immediately update database with awaiting_approval status
      try {
        logger.info("Checking if updateToolCallStatusInDatabase exists", {
          methodExists:
            typeof this.updateToolCallStatusInDatabase === "function",
          thisType: typeof this,
          methodNames: Object.getOwnPropertyNames(Object.getPrototypeOf(this)),
        });

        if (typeof this.updateToolCallStatusInDatabase !== "function") {
          logger.error("updateToolCallStatusInDatabase method is not defined", {
            availableMethods: Object.getOwnPropertyNames(
              Object.getPrototypeOf(this),
            ),
          });
          throw new Error(
            "updateToolCallStatusInDatabase method is not defined",
          );
        }

        await this.updateToolCallStatusInDatabase(
          messageId,
          toolCall.id,
          "awaiting_approval",
        );
      } catch (updateError) {
        logger.error("Failed to update tool call status to awaiting_approval", {
          messageId,
          toolCallId: toolCall.id,
          error: this.extractErrorMessage(updateError),
        });
      }

      const approvalResult = await this.waitForUserApproval(
        toolCall,
        threadId,
        messageId,
        mcpServerId,
      );

      if (!approvalResult.approved) {
        result.status = "error";
        result.error = `Tool execution rejected by user${approvalResult.reason ? `: ${approvalResult.reason}` : ""}`;
        result.userMessage = approvalResult.reason;
        result.executionTime = Date.now() - startTime;

        // Update database with error status for rejected tool call
        try {
          await this.updateToolCallStatusInDatabase(messageId, id, "error");
        } catch (updateError) {
          logger.error(
            "Failed to update tool call status to error (rejected)",
            {
              messageId,
              toolCallId: id,
              error: this.extractErrorMessage(updateError),
            },
          );
        }

        sendToRenderer(EventNames.TOOL_CALL_ERROR, {
          threadId,
          messageId,
          toolCallId: id,
          toolName,
          error: result.error,
        });

        return result;
      }
    }

    // Continue with normal execution - only update status to pending if it's not already awaiting_approval
    if (result.status !== "awaiting_approval") {
      result.status = "pending";
      // Note: Database will be updated by event handlers in provider service
    }

    try {
      // Parse tool arguments
      let parsedArgs: Record<string, unknown>;
      try {
        parsedArgs = JSON.parse(toolArgs);
      } catch (parseError) {
        const error = "Invalid tool arguments: not valid JSON";
        logger.error("Failed to parse tool arguments", {
          toolCallId: id,
          toolName,
          toolArgs,
          parseError,
        });

        result.status = "error";
        result.error = error;

        sendToRenderer(EventNames.TOOL_CALL_ERROR, {
          threadId,
          messageId,
          toolCallId: id,
          toolName,
          error,
        });

        return result;
      }

      // Get server-specific timeout or use default
      const serverTimeout = await this.getServerTimeout(mcpServerId);

      // Execute the tool with retry mechanism
      const toolResult = await this.executeWithRetry(
        mcpServerId,
        toolName,
        parsedArgs,
        serverTimeout,
        id,
      );

      result.status = "success";
      // The callMcpTool method already returns a properly formatted string
      result.result =
        typeof toolResult === "string" ? toolResult : String(toolResult);
      result.executionTime = Date.now() - startTime;

      // Update database with success status
      try {
        await this.updateToolCallStatusInDatabase(messageId, id, "success");
      } catch (updateError) {
        logger.error("Failed to update tool call status to success", {
          messageId,
          toolCallId: id,
          error: this.extractErrorMessage(updateError),
        });
      }

      logger.info("MCP tool call executed successfully", {
        toolCallId: id,
        toolName,
        mcpServerId,
        executionTime: result.executionTime,
      });

      sendToRenderer(EventNames.TOOL_CALL_SUCCESS, {
        threadId,
        messageId,
        toolCallId: id,
        toolName,
        result: result.result,
        executionTime: result.executionTime,
      });
    } catch (error) {
      const errorMessage = this.extractErrorMessage(error);
      result.status = "error";
      result.error = errorMessage;
      result.executionTime = Date.now() - startTime;

      // Update database with error status
      try {
        await this.updateToolCallStatusInDatabase(messageId, id, "error");
      } catch (updateError) {
        logger.error(
          "Failed to update tool call status to error (execution failed)",
          {
            messageId,
            toolCallId: id,
            error: this.extractErrorMessage(updateError),
          },
        );
      }

      logger.error("MCP tool call failed", {
        toolCallId: id,
        toolName,
        error: errorMessage,
        executionTime: result.executionTime,
      });

      sendToRenderer(EventNames.TOOL_CALL_ERROR, {
        threadId,
        messageId,
        toolCallId: id,
        toolName,
        error: errorMessage,
      });
    }

    return result;
  }

  /**
   * Execute multiple tool calls in parallel
   */
  async executeToolCalls(
    toolCalls: OpenAIToolCall[],
    threadId: string,
    messageId: string,
    serverIds: string[],
  ): Promise<ToolCallResult[]> {
    logger.info("Executing multiple MCP tool calls", {
      toolCallCount: toolCalls.length,
      threadId,
      messageId,
      serverIds,
    });

    const executionPromises = toolCalls.map((toolCall) =>
      this.executeToolCall(toolCall, threadId, messageId, serverIds),
    );

    const results = await Promise.allSettled(executionPromises);

    const toolCallResults: ToolCallResult[] = results.map((result, index) => {
      logger.info("MCP tool call result", {
        result,
      });
      if (result.status === "fulfilled") {
        return result.value;
      } else {
        // Handle promise rejection
        const toolCall = toolCalls[index];
        return {
          id: toolCall.id,
          name: toolCall.function.name,
          arguments: toolCall.function.arguments,
          status: "error" as ToolCallStatus,
          error: this.extractErrorMessage(result.reason),
        };
      }
    });

    const successCount = toolCallResults.filter(
      (r) => r.status === "success",
    ).length;
    const errorCount = toolCallResults.filter(
      (r) => r.status === "error",
    ).length;

    logger.info("MCP tool calls execution completed", {
      threadId,
      messageId,
      totalCalls: toolCalls.length,
      successCount,
      errorCount,
    });

    // Emit completion event
    sendToRenderer(EventNames.TOOL_CALL_COMPLETE, {
      threadId,
      messageId,
      completedCalls: successCount + errorCount,
      totalCalls: toolCalls.length,
    });

    return toolCallResults;
  }

  /**
   * Get server-specific timeout configuration
   * Returns 0 if timeout should be disabled, or actual timeout value in ms
   */
  private async getServerTimeout(mcpServerId: string): Promise<number> {
    try {
      const server = await this.mcpService.getMcpServerById(
        {} as Electron.IpcMainEvent,
        mcpServerId,
      );

      // Check if timeout is explicitly set
      if (server?.advancedSettings?.timeout != null) {
        const timeout = server.advancedSettings.timeout;
        // If timeout is 0, return 0 (no timeout)
        // Otherwise return the configured timeout
        return timeout === 0 ? 0 : timeout;
      }

      // Default to 30 seconds if not configured
      return McpToolExecutor.TOOL_CALL_TIMEOUT;
    } catch (error) {
      logger.warn("Failed to get server timeout, using default", {
        mcpServerId,
        error: this.extractErrorMessage(error),
      });
      return McpToolExecutor.TOOL_CALL_TIMEOUT;
    }
  }

  /**
   * Find which MCP server has the specified tool using database cache
   */
  private async findServerForTool(
    toolName: string,
    serverIds: string[],
  ): Promise<string | null> {
    try {
      // Get tools from database cache instead of querying live servers
      for (const serverId of serverIds) {
        try {
          const serverTools =
            await this.mcpService.getToolsFromDatabase(serverId);
          const hasTools = serverTools.some(
            (tool: { name: string }) => tool.name === toolName,
          );
          if (hasTools) {
            return serverId;
          }
        } catch (error) {
          logger.warn("Failed to get tools from database for MCP server", {
            serverId,
            toolName,
            error: this.extractErrorMessage(error),
          });
          // Continue checking other servers
        }
      }
      return null;
    } catch (error) {
      logger.error("Failed to find server for tool in database", {
        toolName,
        serverIds,
        error: this.extractErrorMessage(error),
      });
      return null;
    }
  }

  /**
   * Execute tool with retry mechanism
   */
  private async executeWithRetry(
    mcpServerId: string,
    toolName: string,
    args: Record<string, unknown>,
    serverTimeout: number,
    toolCallId: string,
  ): Promise<unknown> {
    let lastError: Error | null = null;

    for (
      let attempt = 1;
      attempt <= McpToolExecutor.MAX_RETRY_ATTEMPTS;
      attempt++
    ) {
      try {
        logger.info("Attempting tool execution", {
          toolCallId,
          toolName,
          mcpServerId,
          attempt,
          maxAttempts: McpToolExecutor.MAX_RETRY_ATTEMPTS,
        });

        // Execute the tool with timeout (if timeout is 0, don't set timeout)
        const toolResult =
          serverTimeout === 0
            ? await this.executeWithMcpServer(mcpServerId, toolName, args)
            : await Promise.race([
                this.executeWithMcpServer(mcpServerId, toolName, args),
                this.createTimeoutPromise(serverTimeout),
              ]);

        logger.info("Tool execution succeeded", {
          toolCallId,
          toolName,
          mcpServerId,
          attempt,
        });

        return toolResult;
      } catch (error) {
        lastError = error instanceof Error ? error : new Error(String(error));

        logger.warn("Tool execution attempt failed", {
          toolCallId,
          toolName,
          mcpServerId,
          attempt,
          maxAttempts: McpToolExecutor.MAX_RETRY_ATTEMPTS,
          error: this.extractErrorMessage(error),
        });

        // If this was the last attempt, don't wait
        if (attempt < McpToolExecutor.MAX_RETRY_ATTEMPTS) {
          // Wait a bit before retrying (exponential backoff)
          const waitTime = Math.min(1000 * 2 ** (attempt - 1), 5000); // Max 5 seconds
          logger.info("Waiting before retry", {
            toolCallId,
            toolName,
            waitTime,
            nextAttempt: attempt + 1,
          });

          await new Promise((resolve) => setTimeout(resolve, waitTime));
        }
      }
    }

    // All attempts failed
    logger.error("All tool execution attempts failed", {
      toolCallId,
      toolName,
      mcpServerId,
      attempts: McpToolExecutor.MAX_RETRY_ATTEMPTS,
      finalError: this.extractErrorMessage(lastError),
    });

    throw (
      lastError ||
      new Error(
        `Tool execution failed after ${McpToolExecutor.MAX_RETRY_ATTEMPTS} attempts`,
      )
    );
  }

  /**
   * Execute tool with specific MCP server with connection retry
   */
  private async executeWithMcpServer(
    mcpServerId: string,
    toolName: string,
    args: Record<string, unknown>,
  ): Promise<unknown> {
    try {
      // Ensure MCP server is connected before tool execution
      await this.ensureMcpServerConnected(mcpServerId);

      // Call the tool using MCP client
      const result = await this.callMcpTool(mcpServerId, toolName, args);
      return result;
    } catch (error) {
      logger.error("Failed to execute tool with MCP server", {
        mcpServerId,
        toolName,
        args,
        error: this.extractErrorMessage(error),
      });
      throw error;
    }
  }

  /**
   * Ensure MCP server is connected, with 3-attempt retry mechanism
   */
  private async ensureMcpServerConnected(mcpServerId: string): Promise<void> {
    const maxConnectionAttempts = 3;

    for (let attempt = 1; attempt <= maxConnectionAttempts; attempt++) {
      try {
        // Check current connection status
        const clientInfo = await this.mcpService.getConnectedClients(
          {} as Electron.IpcMainEvent,
        );
        const serverClient = clientInfo.find(
          (client) => client.serverId === mcpServerId,
        );

        if (serverClient?.connected) {
          logger.debug("MCP server connection verified", {
            mcpServerId,
            attempt,
          });
          return; // Server is connected, we're good to go
        }

        // Server is not connected, attempt to reconnect
        logger.warn("MCP server not connected, attempting reconnection", {
          mcpServerId,
          attempt,
          maxAttempts: maxConnectionAttempts,
        });

        await this.mcpService.connectToMcpServer(mcpServerId);

        // Wait a bit for connection to stabilize
        await new Promise((resolve) => setTimeout(resolve, 500));

        // Verify connection was successful
        const updatedClientInfo = await this.mcpService.getConnectedClients(
          {} as Electron.IpcMainEvent,
        );
        const reconnectedClient = updatedClientInfo.find(
          (client) => client.serverId === mcpServerId,
        );

        if (reconnectedClient?.connected) {
          logger.info("MCP server reconnected successfully", {
            mcpServerId,
            attempt,
          });
          return;
        }

        // Connection attempt failed
        throw new Error(
          `Failed to connect to MCP server ${mcpServerId} on attempt ${attempt}`,
        );
      } catch (error) {
        const isLastAttempt = attempt === maxConnectionAttempts;

        logger.warn("MCP server connection attempt failed", {
          mcpServerId,
          attempt,
          maxAttempts: maxConnectionAttempts,
          error: this.extractErrorMessage(error),
          isLastAttempt,
        });

        if (isLastAttempt) {
          // All connection attempts failed
          const finalError = new Error(
            `Failed to connect to MCP server ${mcpServerId} after ${maxConnectionAttempts} attempts: ${this.extractErrorMessage(error)}`,
          );

          logger.error("All MCP server connection attempts failed", {
            mcpServerId,
            totalAttempts: maxConnectionAttempts,
            error: this.extractErrorMessage(finalError),
          });

          throw finalError;
        }

        // Wait before next attempt (exponential backoff)
        const waitTime = Math.min(1000 * 2 ** (attempt - 1), 3000); // Max 3 seconds
        logger.info("Waiting before next connection attempt", {
          mcpServerId,
          waitTime,
          nextAttempt: attempt + 1,
        });

        await new Promise((resolve) => setTimeout(resolve, waitTime));
      }
    }
  }

  /**
   * Call MCP tool via client
   */
  private async callMcpTool(
    mcpServerId: string,
    toolName: string,
    args: Record<string, unknown>,
  ): Promise<unknown> {
    logger.info("Calling MCP tool", { mcpServerId, toolName, args });

    try {
      // Get the MCP client for this server from McpService
      const connectedClients = await this.mcpService.getConnectedClients(
        {} as Electron.IpcMainEvent,
      );

      const clientInfo = connectedClients.find(
        (client) => client.serverId === mcpServerId && client.connected,
      );

      if (!clientInfo) {
        throw new Error(`MCP server ${mcpServerId} is not connected`);
      }

      // Get the actual MCP client instance from McpService
      // We need to add a method to get the raw client
      const client = this.getMcpClient(mcpServerId);

      if (!client) {
        throw new Error(`Failed to get MCP client for server ${mcpServerId}`);
      }

      // Call the tool using MCP SDK
      const result = await client.callTool({
        name: toolName,
        arguments: args,
      });

      logger.info("MCP tool call completed successfully", {
        mcpServerId,
        toolName,
        resultType: typeof result,
        hasContent: !!result.content,
        result,
      });

      // Return the tool result content
      // First, try to use structuredContent if available
      if (result.structuredContent) {
        return JSON.stringify(result.structuredContent, null, 2);
      }

      // If no structuredContent, process the content array
      if (result.content && Array.isArray(result.content)) {
        // Extract text content from MCP response
        const textContent = result.content
          .filter((item: { type: string }) => item.type === "text")
          .map((item: { text: string }) => item.text)
          .join("\n");

        // Try to parse the text content as JSON and re-stringify it for better formatting
        try {
          const parsed = JSON.parse(textContent);
          return JSON.stringify(parsed, null, 2);
        } catch {
          // If not valid JSON, return as-is
          return textContent;
        }
      }

      return result.content || result;
    } catch (error) {
      logger.error("Failed to call MCP tool", {
        mcpServerId,
        toolName,
        args,
        error: this.extractErrorMessage(error),
      });
      throw error;
    }
  }

  /**
   * Get MCP client instance from McpService
   */
  private getMcpClient(mcpServerId: string): Client {
    try {
      const clientInfo = this.mcpService.getMcpClientForTool(mcpServerId);

      if (!clientInfo || !clientInfo.connected) {
        throw new Error(
          `MCP client not found or not connected: ${mcpServerId}`,
        );
      }

      return clientInfo.client;
    } catch (error) {
      logger.error("Failed to get MCP client", {
        mcpServerId,
        error: this.extractErrorMessage(error),
      });
      throw error;
    }
  }

  /**
   * Create a timeout promise
   */
  private createTimeoutPromise(timeoutMs: number): Promise<never> {
    return new Promise((_, reject) => {
      setTimeout(() => {
        reject(new Error(`Tool call timed out after ${timeoutMs}ms`));
      }, timeoutMs);
    });
  }

  /**
   * Check if a tool requires user approval based on MCP server settings
   */
  private async checkToolRequiresApproval(
    mcpServerId: string,
  ): Promise<boolean> {
    try {
      const server =
        await this.mcpService.getMcpServerByIdInternal(mcpServerId);
      if (!server || !server.advancedSettings) {
        return false; // Default to automatic execution if no settings
      }

      // If autoUseTool is explicitly false, require approval
      return server.advancedSettings.autoUseTool === false;
    } catch (error) {
      logger.error("Failed to check tool approval requirement", {
        mcpServerId,
        error: this.extractErrorMessage(error),
      });
      return false; // Default to automatic execution on error
    }
  }

  /**
   * Wait for user approval before executing a tool call
   */
  private async waitForUserApproval(
    toolCall: OpenAIToolCall,
    threadId: string,
    messageId: string,
    mcpServerId: string,
  ): Promise<{ approved: boolean; reason?: string }> {
    return new Promise((resolve) => {
      const { id, function: toolFunction } = toolCall;
      const { name: toolName, arguments: toolArgs } = toolFunction;

      logger.info("Waiting for user approval for tool call", {
        toolCallId: id,
        toolName,
        threadId,
        messageId,
        mcpServerId,
      });

      // Emit approval required event to UI
      sendToRenderer(EventNames.TOOL_CALL_APPROVAL_REQUIRED, {
        threadId,
        messageId,
        toolCallId: id,
        toolName,
        toolArguments: toolArgs,
        mcpServerId,
      });

      // Set up event listeners for approval responses
      const approvalHandler = (data: Events[EventNames.TOOL_CALL_APPROVED]) => {
        if (
          data.toolCallId === id &&
          data.messageId === messageId &&
          data.threadId === threadId
        ) {
          logger.info("Tool call approved by user", {
            toolCallId: id,
            toolName,
            threadId,
            messageId,
          });

          // Update database status to pending after approval
          this.updateToolCallStatusInDatabase(messageId, id, "pending").catch(
            (updateError) => {
              logger.error(
                "Failed to update tool call status to pending after approval",
                {
                  messageId,
                  toolCallId: id,
                  error: this.extractErrorMessage(updateError),
                },
              );
            },
          );

          // Clean up listeners
          emitter.off(EventNames.TOOL_CALL_APPROVED, approvalHandler);
          emitter.off(EventNames.TOOL_CALL_REJECTED, rejectionHandler);

          resolve({ approved: true });
        }
      };

      const rejectionHandler = (
        data: Events[EventNames.TOOL_CALL_REJECTED],
      ) => {
        if (
          data.toolCallId === id &&
          data.messageId === messageId &&
          data.threadId === threadId
        ) {
          logger.info("Tool call rejected by user", {
            toolCallId: id,
            toolName,
            threadId,
            messageId,
            reason: data.reason,
          });

          // Clean up listeners
          emitter.off(EventNames.TOOL_CALL_APPROVED, approvalHandler);
          emitter.off(EventNames.TOOL_CALL_REJECTED, rejectionHandler);

          resolve({ approved: false, reason: data.reason });
        }
      };

      // Register event listeners (no timeout - wait indefinitely for user approval)
      emitter.on(EventNames.TOOL_CALL_APPROVED, approvalHandler);
      emitter.on(EventNames.TOOL_CALL_REJECTED, rejectionHandler);
    });
  }

  /**
   * Update tool call status in database
   */
  private async updateToolCallStatusInDatabase(
    messageId: string,
    toolCallId: string,
    status: ToolCallStatus,
  ): Promise<void> {
    logger.info("updateToolCallStatusInDatabase called", {
      messageId,
      toolCallId,
      status,
    });

    try {
      // Get current message to retrieve existing tool calls
      const currentMessage = await this.messageService.getMessageById(
        {} as Electron.IpcMainEvent,
        messageId,
      );

      if (!currentMessage || !currentMessage.toolCalls) {
        logger.warn("No message or tool calls found for status update", {
          messageId,
          toolCallId,
          status,
        });
        return;
      }

      // Parse existing tool calls
      let toolCalls: ToolCallResult[];
      try {
        toolCalls = JSON.parse(currentMessage.toolCalls);
      } catch (error) {
        logger.error("Failed to parse tool calls JSON", {
          messageId,
          toolCallId,
          toolCalls: currentMessage.toolCalls,
          error: this.extractErrorMessage(error),
        });
        return;
      }

      // Update the specific tool call status
      const updatedToolCalls = toolCalls.map((tc) => {
        if (tc.id === toolCallId) {
          return { ...tc, status };
        }
        return tc;
      });

      // Save updated tool calls back to database
      await this.messageService.updateMessage(
        {} as Electron.IpcMainEvent,
        messageId,
        {
          toolCalls: JSON.stringify(updatedToolCalls),
        },
      );

      logger.info("Tool call status updated in database", {
        messageId,
        toolCallId,
        status,
      });
    } catch (error) {
      logger.error("Failed to update tool call status in database", {
        messageId,
        toolCallId,
        status,
        error: this.extractErrorMessage(error),
      });
    }
  }

  /**
   * Extract error message from error object
   */
  private extractErrorMessage(error: unknown): string {
    if (error instanceof Error) {
      return error.message;
    }
    if (typeof error === "string") {
      return error;
    }
    return "Unknown error occurred";
  }
}
